Using an Ops Service
Examine Ops services and how to construct a basic chatbot.
We'll cover the following
We are not going to go into complete detail about this service, as we have covered how gRPC works in previous sections. As this service makes gRPC or REST calls to other services, let's talk about the calls that need to be implemented.
The protocol buffer service definition is as follows:
For our example service, these RPCs are targeted at a single deployed instance, but in a production environment, this would work on multiple entities that exist on a site.
This allows users to get some information quickly, such as the following:
See the traces we have in a certain time period, and the ability to filter by tags (such as
error).Retrieve basic trace data and the Jaeger URL of a trace given a trace ID.
Change the sampling type and rate for traces in the service.
Tell us what version has been deployed according to Prometheus.
Display any alerts that Prometheus shows are firing.
We can read the code on how this is implemented below:
/
We include a README file that goes over the basic architecture, but it is your standard gRPC service that makes calls using gRPC to the Petstore service/Jaeger and REST calls to Prometheus.
Now, let's jump into something new, writing the basic Slack bot.
Building a basic chatbot#
Go has a few clients that can interact with popular chat services such as Slack, either as a general Slack client or as a ChatOps-focused bot.
We have found that it is best to have an architecture that separates the bot from the operations that we want to perform. This allows other tooling in other languages to access the capabilities. By keeping the chatbot separate, we can focus on a single type of chat service and use all its features instead of only features shared by each chat service client.
For this reason, we will use the slack-go package to interact with Slack.
Our bot will be basic, simply listening to when someone mentions our bot in a message. This is called an AppMention event. Slack supports other events and has events specifically for commands that we can install. In our case, we just want to answer whenever we are mentioned, but slack-go has many other capabilities we will not explore.
Let's create a package called bot and add some imports:
Details on our third-party package are as follows:
slackis what is used to build a basic client.slackeventsdetails the various events we can receive.socketmodeprovides a method of connecting to Slack from a bot that is behind a firewall.
Let's create a type that can handle an event we receive:
HandleFunc receives a message that can be used to write to channels and get information about the message that was received.
We have also defined a register type that is used to register a HandleFunc with a regular expression (regex). The regex will be used to determine whether the message should be handled by that specific HandleFunc.
Let's define the Message type:
This contains information about the Slack user who sent the message, information about the AppMention event and the cleaned-up text that the user sent (remove the @User text and leading/trailing spaces).
Now, let's define our Bot type and its constructor:
This code contains our clients that will be used to interact with Slack, a context for canceling our bot's goroutines, defaultHandler for handling the case where no regex matches happen, and a list of registrations that we check on any message receipt.
We now need some methods to start and stop our bot:
This starts our event loop and calls RunContext to listen to our event stream. We cancel our bot using the supplied context.Bot. Start() blocks until Stop() is called.
Our next method will allow us to register our regexes and their handlers:
In this code, if we don't supply a regex, then HandleFunc is used as the default handler when no regexes match. We can only have one default handler. When the bot checks a message, it matches regexes in the order they are added; the first match wins.
Now, let's look at our event loop:
Here, we pull events off the socketmode client. We switch on the type of event. For our purposes, we are only interested in two types of events:
Error connecting to the
WebSocketAn
EventTypeEventsAPIevent
An EventTypeEventsAPI type is an interface that we turn into its concrete type, slackevents.EventsAPIEvent. We acknowledge receipt of the event and send the event to be handled by a method called appMentioned().
There are other events we might be interested in. We can find a list of the official events supported by Slack here.
The Go package event support may be slightly different and can be found here.
Now, let's build appMentioned():
Slack events are events wrapped inside events, so it takes a little decoding to get to the information we need. This code looks at the event data type and uses that information to know what type to decode.
For appMentioned(), this should always be slackevents.CallbackEvent, which decodes its .Data field into a *slackevents.EventsAPICallbackEvent type. That has .InnerEvent, which can decode into a few other event types. We are only interested if it decodes to *slackevents.AppMentionEvent.
If it does, we call another internal method called makeMsg() that returns the message type we defined earlier. We are going to skip the makeMsg() implementation, as it has some deep JSON data conversions that, due to the nature of JSON, are a little convoluted and uninteresting. We can lift it from the linked code.
We then loop through our regexes, looking for a match. If we find one, we call HandleFunc on that message and stop processing. If we don't find a match, we call defaultHandler, if it exists.
Now, we have a bot that can listen for when it is mentioned in a message and dispatch the message to a handler.
Environment Architecture
Creating Event Handlers